.NET Core 中的 EventCounters 您所在的位置:网站首页 counter sample翻译 .NET Core 中的 EventCounters

.NET Core 中的 EventCounters

2023-11-05 05:33| 来源: 网络整理| 查看: 265

.NET 中的 EventCounters 项目 04/08/2023

本文适用于: ✔️ .NET Core 3.0 SDK 及更高版本

EventCounters 是 .NET API,用于轻量级、跨平台、准实时性能指标收集。 EventCounters 作为 Windows 上 .NET 框架的“性能计数器”的跨平台替代项添加。 本文将介绍什么是 EventCounters,如何实现它们,以及如何使用它们。

.NET 运行时和几个 .NET 库使用从 .NET Core 3.0 开始引入的 EventCounters 发布基本诊断信息。 除了 .NET 运行时提供的 EventCounters 外,你还可以选择实现自己的 EventCounters。 可使用 EventCounters 跟踪各种指标。 在 .NET 中的已知 EventCounters 中详细了解其信息

EventCounters 作为 EventSource 的一部分实时自动定期推送到侦听器工具。 与 EventSource 上所有其他事件一样,可以通过 EventListener 和 EventPipe 在进程内和进程外使用它们。 本文重点介绍 EventCounters 的跨平台功能,并特意排除 PerfView 和 ETW(Windows 事件跟踪)- 尽管两者都可用于 EventCounters。

EventCounter API 概述

有两种主要类别的 EventCounters。 某些计数器用于计算“比率”的值,例如异常总数、GC 总数和请求总数。 其他计数器是“快照”值,例如堆使用情况、CPU 使用率和工作集大小。 在这两个类别的计数器中,各有两种类型的计数器,由获取值的方式区分。 轮询计数器通过回调检索其值,非轮询计数器直接在计数器实例上设置其值。

计数器由以下实现表示:

EventCounter IncrementingEventCounter PollingCounter IncrementingPollingCounter

事件侦听器指定测量间隔的时长。 在每个间隔结束时,每个计数器的值将传输到侦听器。 计数器的实现确定使用哪些 API 和计算来生成每个间隔的值。

EventCounter 记录一组值。 EventCounter.WriteMetric 方法将新值添加到集。 在每个间隔中,将计算集的统计摘要,如最小值、最大值和平均值。 dotnet-counters 工具将始终显示平均值。 EventCounter 用于描述一组离散的操作。 常见用法包括监视最近 IO 操作的平均大小(以字节为单位)或一组金融交易的平均货币价值。

IncrementingEventCounter 记录每个时间间隔的运行总计。 IncrementingEventCounter.Increment 方法添加到总计。 例如,如果在一段间隔内调用三次 Increment(),其值分别为 1、2 和 5,则此间隔的计数器值将报告运行总计 8。 dotnet-counters 工具将比率显示为记录的总计/时间。 IncrementingEventCounter 用于测量操作发生的频率,例如每秒处理的请求数。

PollingCounter 使用回调来确定报告的值。 在每个时间间隔中,调用用户提供的回调函数,然后返回值用作计数器值。 可以使用 PollingCounter 从外部源查询指标,例如获取磁盘上的当前可用字节。 它还可用于报告应用程序可按需计算的自定义统计信息。 示例包括报告最近请求延迟的第 95 个百分位,或缓存的当前命中或错过比率。

IncrementingPollingCounter 使用回调来确定报告的增量值。 对于每个时间间隔,调用回调,然后当前调用与最后一个调用之间的差值是报告的值。 dotnet-counters 工具始终将比率显示为报告的值/时间。 如果不可在每次发生事件时调用 API,但可以查询事件总数,则此计数器很有用。 例如,可以报告每秒写入文件的字节数,即使每次写入字节时没有通知。

实现 EventSource

下面的代码实现作为命名 "Sample.EventCounter.Minimal" 提供程序公开的示例 EventSource。 此源包含表示请求处理时间的 EventCounter。 此类计数器具有名称(即其在源中的唯一 ID)和显示名称,这两个名称都可由侦听器工具(如 dotnet-counter)使用。

using System.Diagnostics.Tracing; [EventSource(Name = "Sample.EventCounter.Minimal")] public sealed class MinimalEventCounterSource : EventSource { public static readonly MinimalEventCounterSource Log = new MinimalEventCounterSource(); private EventCounter _requestCounter; private MinimalEventCounterSource() => _requestCounter = new EventCounter("request-time", this) { DisplayName = "Request Processing Time", DisplayUnits = "ms" }; public void Request(string url, long elapsedMilliseconds) { WriteEvent(1, url, elapsedMilliseconds); _requestCounter?.WriteMetric(elapsedMilliseconds); } protected override void Dispose(bool disposing) { _requestCounter?.Dispose(); _requestCounter = null; base.Dispose(disposing); } }

可以使用 dotnet-counters ps 来显示可监视的 .NET 进程的列表:

dotnet-counters ps 1398652 dotnet C:\Program Files\dotnet\dotnet.exe 1399072 dotnet C:\Program Files\dotnet\dotnet.exe 1399112 dotnet C:\Program Files\dotnet\dotnet.exe 1401880 dotnet C:\Program Files\dotnet\dotnet.exe 1400180 sample-counters C:\sample-counters\bin\Debug\netcoreapp3.1\sample-counters.exe

将 EventSource 名称传递到 --counters 选项,以开始监视计数器:

dotnet-counters monitor --process-id 1400180 --counters Sample.EventCounter.Minimal

以下示例显示监视器输出:

Press p to pause, r to resume, q to quit. Status: Running [Samples-EventCounterDemos-Minimal] Request Processing Time (ms) 0.445

按 q 停止监视命令。

条件计数器

实现 EventSource 时,通过 Command 值 EventCommand.Enable 调用 EventSource.OnEventCommand 方法时,可以有条件地实例化包含计数器。 要仅在计数器实例为 null 时将其安全地实例化,请使用 null 合并赋值运算符。 此外,自定义方法可以计算 IsEnabled 方法,以确定是否启用了当前事件源。

using System.Diagnostics.Tracing; [EventSource(Name = "Sample.EventCounter.Conditional")] public sealed class ConditionalEventCounterSource : EventSource { public static readonly ConditionalEventCounterSource Log = new ConditionalEventCounterSource(); private EventCounter _requestCounter; private ConditionalEventCounterSource() { } protected override void OnEventCommand(EventCommandEventArgs args) { if (args.Command == EventCommand.Enable) { _requestCounter ??= new EventCounter("request-time", this) { DisplayName = "Request Processing Time", DisplayUnits = "ms" }; } } public void Request(string url, float elapsedMilliseconds) { if (IsEnabled()) { _requestCounter?.WriteMetric(elapsedMilliseconds); } } protected override void Dispose(bool disposing) { _requestCounter?.Dispose(); _requestCounter = null; base.Dispose(disposing); } }

提示

条件计数器是有条件地实例化的计数器,即微优化。 对于通常不使用计数器的场景,运行时采用此模式来节省不到一毫秒的时间。

.NET Core 运行时示例计数器

在 .NET Core 运行时中有许多很好的示例实现。 下面是跟踪应用程序工作集大小的计数器的运行时实现。

var workingSetCounter = new PollingCounter( "working-set", this, () => (double)(Environment.WorkingSet / 1_000_000)) { DisplayName = "Working Set", DisplayUnits = "MB" };

PollingCounter 报告映射到应用的进程(工作集)的当前物理内存量,因为它在一个时刻捕获一个指标。 轮询值的回调是提供的 lambda 表达式,这只是对 System.Environment.WorkingSet API 的调用。 DisplayName 和 DisplayUnits 是可选属性,可以设置它们,帮助计数器的使用者方更清楚地显示值。 例如,dotnet-counters 使用这些属性来显示计数器名称的更具有显示友好性的版本。

重要

DisplayName 属性未本地化。

对于 PollingCounter 和 IncrementingPollingCounter,无需执行任何其他操作。 它们本身都按使用者请求的时间间隔轮询值。

下面是使用 IncrementingPollingCounter 实现的运行时计数器的示例。

var monitorContentionCounter = new IncrementingPollingCounter( "monitor-lock-contention-count", this, () => Monitor.LockContentionCount ) { DisplayName = "Monitor Lock Contention Count", DisplayRateTimeScale = TimeSpan.FromSeconds(1) };

IncrementingPollingCounter 使用 Monitor.LockContentionCount API 报告锁争用数总计的增量。 DisplayRateTimeScale 属性可选,但使用它时,它可以提供有关计数器最佳显示时间间隔的提示。 例如,锁争用计数最好显示为“每秒计数”,因此其 DisplayRateTimeScale 设置为一秒。 可为不同类型的比率计数器调整显示比率。

注意

DisplayRateTimeScale 不由 dotnet-counters 使用,不需要事件侦听器即可使用它。

在 .NET 运行时存储库中,有更多的计数器实现可用作参考。

并发

提示

EventCounters API 不能保证线程安全性。 当传递到 PollingCounter 或 IncrementingPollingCounter 实例的委托由多个线程调用时,你有责任保证委托的线程安全性。

例如,请考虑使用以下 EventSource 来跟踪请求。

using System; using System.Diagnostics.Tracing; public class RequestEventSource : EventSource { public static readonly RequestEventSource Log = new RequestEventSource(); private IncrementingPollingCounter _requestRateCounter; private long _requestCount = 0; private RequestEventSource() => _requestRateCounter = new IncrementingPollingCounter("request-rate", this, () => _requestCount) { DisplayName = "Request Rate", DisplayRateTimeScale = TimeSpan.FromSeconds(1) }; public void AddRequest() => ++ _requestCount; protected override void Dispose(bool disposing) { _requestRateCounter?.Dispose(); _requestRateCounter = null; base.Dispose(disposing); } }

可以从请求处理程序调用 AddRequest() 方法,并且 RequestRateCounter 按计数器使用者指定的间隔轮询值。 但是,AddRequest() 方法可以同时由多个线程调用,将争用条件置于 _requestCount。 增加 _requestCount 的线程安全替代方法是使用 Interlocked.Increment。

public void AddRequest() => Interlocked.Increment(ref _requestCount);

若要防止破坏(在 32 位体系结构上)对 long 字段 _requestCount 的读取,请使用 Interlocked.Read。

_requestRateCounter = new IncrementingPollingCounter("request-rate", this, () => Interlocked.Read(ref _requestCount)) { DisplayName = "Request Rate", DisplayRateTimeScale = TimeSpan.FromSeconds(1) }; 使用 EventCounters

EventCounters 的使用方式主要有两种:进程内或进程外。 EventCounters 的使用可以分为三层不同的使用技术。

通过 ETW 或 EventPipe 在原始流中传输事件:

ETW API 附带 Windows OS,EventPipe 可作为 .NET API 或诊断 IPC 协议进行访问。

将二进制事件流解码为事件:

TraceEvent 库可处理 ETW 和 EventPipe 流格式。

命令行和 GUI 工具:

PerfView(ETW 或 EventPipe)、dotnet-counters(仅 EventPipe)和 dotnet-monitor(仅 EventPipe)等工具。

进程外使用

在进程外使用 EventCounters 是一种常见方法。 你可以使用 dotnet-counters 通过 EventPipe 以跨平台方式使用它们。 dotnet-counters 工具是一个跨平台 dotnet CLI 全局工具,可用于监视计数器值。 要了解如何使用 dotnet-counters 监视计数器,请参阅 dotnet-counters 或浏览使用 EventCounters 衡量性能教程。

dotnet-trace

dotnet-trace 工具可用于通过 EventPipe 使用计数器数据。 下面是使用 dotnet-trace 收集计数器数据的一个示例。

dotnet-trace collect --process-id Sample.EventCounter.Minimal:0:0:EventCounterIntervalSec=1

有关如何随着时间的推移收集计数器值的详细信息,请参阅 dotnet-trace 文档。

Azure Application Insights

EventCounters 可由 Azure Monitor 使用,特别是 Azure Application Insights。 可以添加和删除计数器,并且可以自由指定自定义计数器或已知计数器。 有关详细信息,请参阅自定义要收集的计数器。

dotnet-monitor

dotnet-monitor 工具可以更轻松地以自动化方式远程访问来自 .NET 进程的诊断信息。 除跟踪外,它还可以监视指标、收集内存转储和收集 GC 转储。 它以 CLI 工具和 docker 映像的形式发布。 它公开了 REST API,以及通过 REST 调用发生的诊断项目集合。

有关详细信息,请参阅 dotnet-monitor。

进程内使用

可以通过 EventListener API 使用计数器值。 EventListener 是使用由应用程序中 EventSource 的所有实例编写的任何事件的一种进程内方法。 有关如何使用 EventListener API 的详细信息,请参阅 EventListener。

首先,需要启用生成计数器值的 EventSource。 替代 EventListener.OnEventSourceCreated 方法以在创建 EventSource 时获取通知,如果对于 EventCounters 这是正确的 EventSource,则可在其上调用 EventListener.EnableEvents。 下面是示例替代:

protected override void OnEventSourceCreated(EventSource source) { if (!source.Name.Equals("System.Runtime")) { return; } EnableEvents(source, EventLevel.Verbose, EventKeywords.All, new Dictionary() { ["EventCounterIntervalSec"] = "1" }); } 代码示例

下面是一个示例 EventListener 类,它打印 .NET 运行时的 EventSource 的所有计数器名称和值,用于每秒发布其内部计数器 (System.Runtime)。

using System; using System.Collections.Generic; using System.Diagnostics.Tracing; public class SimpleEventListener : EventListener { public SimpleEventListener() { } protected override void OnEventSourceCreated(EventSource source) { if (!source.Name.Equals("System.Runtime")) { return; } EnableEvents(source, EventLevel.Verbose, EventKeywords.All, new Dictionary() { ["EventCounterIntervalSec"] = "1" }); } protected override void OnEventWritten(EventWrittenEventArgs eventData) { if (!eventData.EventName.Equals("EventCounters")) { return; } for (int i = 0; i < eventData.Payload.Count; ++ i) { if (eventData.Payload[i] is IDictionary eventPayload) { var (counterName, counterValue) = GetRelevantMetric(eventPayload); Console.WriteLine($"{counterName} : {counterValue}"); } } } private static (string counterName, string counterValue) GetRelevantMetric( IDictionary eventPayload) { var counterName = ""; var counterValue = ""; if (eventPayload.TryGetValue("DisplayName", out object displayValue)) { counterName = displayValue.ToString(); } if (eventPayload.TryGetValue("Mean", out object value) || eventPayload.TryGetValue("Increment", out value)) { counterValue = value.ToString(); } return (counterName, counterValue); } }

如下所示,调用 EnableEvents 时必须确保在 filterPayload 参数中设置 "EventCounterIntervalSec" 参数。 否则,计数器将无法清空值,因为它不知道应清空哪个时间间隔。

另请参阅 dotnet-counters dotnet-trace EventCounter EventListener EventSource


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有